#include <cppunit/config/SourcePrefix.h>

#include "Table.h"
#include "TestTable.h"

void TableTest::setUp() {
	std::cout << "TestTable: setUp()\n";
}

void TableTest::tearDown() {
}

void TableTest::printTable(Table<std::string, int> table) {
	// The top row should be the headers
	std::vector<std::string>& headers = table.getHeaders();
	for (unsigned int i = 0; i < headers.size(); i++) {
		std::cout << headers.at(i) << " ";
	}
	std::cout << '\n';

	std::vector<Table<std::string, int>::Column> columns = table.getColumns();
	// The following rows are the entries
	for (int i = 0; i < table.getNumRows(); i++) {
		// Print in row-major order
		for (int j = 0; j < columns.size(); j++) {
			Table<std::string, int>::Column& column = columns.at(j);

			// Ensure the column has #i
			if (i >= column.size()) {
				std::cout << "  "; // not a good formatting
				break;
			}

			// Here, we are ensured that the column has #i
			std::cout << column.at(i) << " ";
		}
		std::cout << '\n';
	}
}

Table<std::string, int>& TableTest::createTableWith5RowsAB() {
	Table<std::string, int>* table1 = new Table<std::string, int>("a", "b");

	bool isInsertSuccessful;
	// Add 5 entries into the column of "a"
	for (int i = 0; i < 5; i++) {
		isInsertSuccessful = table1->appendEntry("a", i);
		CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);
	}
	// Add 5 entries into the column of "b"
	for (int i = 0; i < 5; i++) {
		isInsertSuccessful = table1->appendEntry("b", i);
		CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);
	}

	return *table1;
}

// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( TableTest );

// function to test the constructor
void TableTest::testConstructor() {
	std::cout << "TestTable: testConstructor()\n";
	// Just to ensure it does not produce errors
	Table<std::string, int> table1;
	Table<std::string, int> table2 = Table<std::string, int>("a", "b");
}

// function to test the column insertion
void TableTest::testInsertColumn() {
	std::cout << "TestTable: testInsertColumn()\n";
	Table<std::string, int> table1;

	// insert "a" first time
	bool isInsertSuccessful = table1.insertColumn("a");
	CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);

	// insert "b" first time
	isInsertSuccessful = table1.insertColumn("b");
	CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);

	// insert "a" second time, should not be inserted
	isInsertSuccessful = table1.insertColumn("a");
	CPPUNIT_ASSERT_EQUAL(false, isInsertSuccessful);
}

// Tests the insertEntry() function
void TableTest::testAppendEntry() {
	std::cout << "TestTable: testAppendEntry()\n";
	Table<std::string, int> table1 = Table<std::string, int>("a", "b");

	bool isInsertSuccessful;
	// Add 5 entries into the column of "a"
	for (int i = 0; i < 5; i++) {
		isInsertSuccessful = table1.appendEntry("a", i);
		CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);
	}
	// Add 5 entries into the column of "b"
	for (int i = 0; i < 5; i++) {
		isInsertSuccessful = table1.appendEntry("b", i);
		CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);
	}

	
	// Create new empty table
	Table<std::string, int> table2 = Table<std::string, int>();
	std::string synonym1 = "a";
	table2.insertColumn(synonym1);

	// Insert an entry into row 0 for "a"
	isInsertSuccessful = table2.appendEntry(synonym1, 0);
	CPPUNIT_ASSERT_EQUAL(true, isInsertSuccessful);
}

// Tests the getEntry() function
void TableTest::testGetEntry() {
	std::cout << "TestTable: testGetEntry()\n";
	Table<std::string, int>& table1 = createTableWith5RowsAB();

	int item;
	// Try to get the 5 entries in "a"
	for (int i = 0; i < 5; i++) {
		item = table1.getEntry("a", i);
		CPPUNIT_ASSERT_EQUAL(i, item);
	}
	// Try to get the 5 entries in "b"
	for (int i = 0; i < 5; i++) {
		item = table1.getEntry("b", i);
		CPPUNIT_ASSERT_EQUAL(i, item);
	}

	/**--------------------------------------------
	 *	Exceptions (out of range)
	 *---------------------------------------------*/

	// Try to get a non-existant entry using a non-existant header "c"
	CPPUNIT_ASSERT_THROW(table1.getEntry("c", 0), std::out_of_range);

	// Try to get a non-existant entry using a negative rowIndex
	CPPUNIT_ASSERT_THROW(table1.getEntry("b", -1), std::out_of_range);

	// Try to get a non-existant entry using a rowIndex >= size
	CPPUNIT_ASSERT_THROW(table1.getEntry("b", 5), std::out_of_range);
}

// Tests the getColumnSize() function
void TableTest::testGetColumnSize() {
	std::cout << "TestTable: testGetColumnSize()\n";
	Table<std::string, int>& table1 = createTableWith5RowsAB();

	int columnSize;
	// Get column size of "a"
	columnSize = table1.getColumnSize("a");
	CPPUNIT_ASSERT_EQUAL(5, columnSize);

	// Get column size of "b"
	columnSize = table1.getColumnSize("b");
	CPPUNIT_ASSERT_EQUAL(5, columnSize);

	/**--------------------------------------
	 *	Failures
	 *---------------------------------------*/
	 // Get column size of non-existant column "c"
	columnSize = table1.getColumnSize("c");
	CPPUNIT_ASSERT_EQUAL(table1.ERROR_GETCOLUMNSIZE_NOSUCHHEADER, columnSize);
}

// Tests the getNumRows() function
void TableTest::testGetNumRows() {
	std::cout << "TestTable: testGetNumRows()\n";
	Table<std::string, int> table1 = Table<std::string, int>();
	CPPUNIT_ASSERT_EQUAL(0, table1.getNumRows());

	// Insert empty columns
	table1.insertColumn("a");
	table1.insertColumn("b");
	CPPUNIT_ASSERT_EQUAL(0, table1.getNumRows());

	/**----------------------------------
	 *	Adding entries
	 *-----------------------------------*/
	// Insert some entries
	// Add 5 entries into the column of "a"
	for (int i = 0; i < 5; i++) {
		table1.appendEntry("a", i);
	}
	CPPUNIT_ASSERT_EQUAL(5, table1.getNumRows());

	// Add 4 entries into the column of "b"
	for (int i = 0; i < 4; i++) {
		table1.appendEntry("b", i);
	}
	CPPUNIT_ASSERT_EQUAL(5, table1.getNumRows());

	/**----------------------------------
	 *	Deleting rows
	 *-----------------------------------*/
	std::cout << "TestTable: before deleting row 4, table is:\n";
	printTable(table1);
	// At this point, table1 has 5 items in "a" and 4 in "b"
	table1.deleteRow(4);
	CPPUNIT_ASSERT_EQUAL(4, table1.getNumRows());
	std::cout << "TestTable: after deleting row 4, table is:\n";
	printTable(table1);
}

// Tests the getHeaders() function
void TableTest::testGetHeaders() {
	Table<std::string, int> *table = &createTableWith5RowsAB();

	std::vector<std::string>* headers = &table->getHeaders();
	CPPUNIT_ASSERT_EQUAL(2, (int)headers->size());

	// Insert a new column "c"
	table->insertColumn("c");
	// Ensure that the insertion does not affect this headers, because it should be a copy
	CPPUNIT_ASSERT_EQUAL(2, (int)headers->size());
	headers = &table->getHeaders();
	CPPUNIT_ASSERT_EQUAL(3, (int)headers->size());
}

// Tests the getColumns() function
void TableTest::testGetColumns() {
	Table<std::string, int> *table = &createTableWith5RowsAB();

	std::vector<Table<std::string,int>::Column>* columns = &table->getColumns();
	CPPUNIT_ASSERT_EQUAL(2, (int)columns->size());

	// Insert a new column "c"
	table->insertColumn("c");
	// Ensure that the insertion does not affect this headers, because it should be a copy
	CPPUNIT_ASSERT_EQUAL(2, (int)columns->size());
	columns = &table->getColumns();
	CPPUNIT_ASSERT_EQUAL(3, (int)columns->size());
}

// Tests the getColumn() function
void TableTest::testGetColumn() {
	Table<std::string, int> *table = &createTableWith5RowsAB();

	Table<std::string, int>::Column *column = &table->getColumn("a");
	CPPUNIT_ASSERT_EQUAL(5, (int)column->size());

	// Delete row 0
	table->deleteRow(0);
	// Ensure that the deletion did not affect the column because it should be a copy
	CPPUNIT_ASSERT_EQUAL(5, (int)column->size());

	// Check column "b"
	column = &table->getColumn("b");
	// "b" should have 4 entries only now
	CPPUNIT_ASSERT_EQUAL(4, (int)column->size());

	// Get a non-existant column "c"
	column = &table->getColumn("c");
	// Should be an empty column returned
	CPPUNIT_ASSERT_EQUAL(0, (int)column->size());
}

// Tests the getColumnItemCountMap() function
void TableTest::testGetColumnItemCountMap() {
	Table<std::string, int> *table = new Table<std::string, int>();

	// Should get an empty map when trying to grab the ColumnItemCountMap of a non-existant column
	Table<std::string, int>::ColumnItemCountMap *columnItemCountMap = &table->getColumnItemCountMap("a");
	CPPUNIT_ASSERT_EQUAL(0, (int)columnItemCountMap->size());

	// Should get a map for "a", but it will be empty, because no entries have been inserted
	table->insertColumn("a");
	columnItemCountMap = &table->getColumnItemCountMap("a");
	CPPUNIT_ASSERT_EQUAL(0, (int)columnItemCountMap->size());

	// Insert some entries for "a"
	table->appendEntry("a", 0);
	table->appendEntry("a", 0);
	table->appendEntry("a", 1);
	table->appendEntry("a", 1);
	// Ensure that the insertions do not affect our current columnItemCountMap because it should be a copy
	CPPUNIT_ASSERT_EQUAL(0, (int)columnItemCountMap->size());
	columnItemCountMap = &table->getColumnItemCountMap("a");
	// Should be a count map containing (0,2) and (1,2) representing that there are 2 0's and 2 1's
	CPPUNIT_ASSERT_EQUAL(2, (int)columnItemCountMap->size());
	CPPUNIT_ASSERT_EQUAL(2, columnItemCountMap->at(0));
	CPPUNIT_ASSERT_EQUAL(2, columnItemCountMap->at(1));

	// Now, delete row 3 containing the item 1
	table->deleteRow(3);
	columnItemCountMap = &table->getColumnItemCountMap("a");
	// Should be a count map containing (0,2) and (1,1) representing that there are 2 0's and 1 1's
	CPPUNIT_ASSERT_EQUAL(2, (int)columnItemCountMap->size());
	CPPUNIT_ASSERT_EQUAL(2, columnItemCountMap->at(0));
	CPPUNIT_ASSERT_EQUAL(1, columnItemCountMap->at(1));
	// Now, delete row 2 containing the item 1
	table->deleteRow(2);
	columnItemCountMap = &table->getColumnItemCountMap("a");
	// Should be a count map containing (0,2) only, representing that there are 2 0's
	CPPUNIT_ASSERT_EQUAL(1, (int)columnItemCountMap->size());
	CPPUNIT_ASSERT_EQUAL(2, columnItemCountMap->at(0));
	// Delete rows 1 and 0
	table->deleteRow(1);
	table->deleteRow(0);
	columnItemCountMap = &table->getColumnItemCountMap("a");
	// Should be an empty count map
	CPPUNIT_ASSERT_EQUAL(0, (int)columnItemCountMap->size());
}

// Tests the duplicateRow() function
void TableTest::testDuplicateRow() {
	std::cout << "TestTable: testDuplicateRow()\n";
	Table<std::string, int>& table1 = createTableWith5RowsAB();

	int lastDuplicatedRow;
	// Duplicate the 0th row 1 time
	lastDuplicatedRow = table1.duplicateRow(0, 1);
	CPPUNIT_ASSERT_EQUAL(6, lastDuplicatedRow);
	CPPUNIT_ASSERT_EQUAL(6, table1.getColumnSize("a"));
	CPPUNIT_ASSERT_EQUAL(6, table1.getColumnSize("b"));

	lastDuplicatedRow = table1.duplicateRow(3, 5);
	CPPUNIT_ASSERT_EQUAL(11, lastDuplicatedRow);
	CPPUNIT_ASSERT_EQUAL(11, table1.getColumnSize("a"));
	CPPUNIT_ASSERT_EQUAL(11, table1.getColumnSize("b"));

	printTable(table1);

	/**--------------------------------------
	 *	Failures
	 *---------------------------------------*/
	// Duplicate a negative rowIndex
	lastDuplicatedRow = table1.duplicateRow(-1, 1);
	CPPUNIT_ASSERT_EQUAL(table1.ERROR_DUPLICATION_BADROWINDEX, lastDuplicatedRow);

	// Duplicate a positive rowIndex that doesn't exist
	lastDuplicatedRow = table1.duplicateRow(11, 1);
	CPPUNIT_ASSERT_EQUAL(table1.ERROR_DUPLICATION_BADROWINDEX, lastDuplicatedRow);

	// Duplicate a rowIndex that exists 0 times
	lastDuplicatedRow = table1.duplicateRow(5, 0);
	CPPUNIT_ASSERT_EQUAL(table1.ERROR_DUPLICATION_BADNUMOFTIMES, lastDuplicatedRow);

	// Duplicate a rowIndex that exists negative times
	lastDuplicatedRow = table1.duplicateRow(5, -1);
	CPPUNIT_ASSERT_EQUAL(table1.ERROR_DUPLICATION_BADNUMOFTIMES, lastDuplicatedRow);
	
	printTable(table1);
}

// Tests the deleteRow() function
void TableTest::testDeleteRow() {
	std::cout << "TestTable: testDeleteRow()\n";
	Table<std::string, int>& table1 = createTableWith5RowsAB();
	
	std::cout << "TestTable: before deletion, table is:\n";
	printTable(table1);

	bool isDeleteSuccessful;
	while (table1.getNumRows() > 0) {
		isDeleteSuccessful = table1.deleteRow(0);
		CPPUNIT_ASSERT_EQUAL(true, isDeleteSuccessful);

		std::cout << "TestTable: after deleting the first row, table is:\n";
		printTable(table1);
	}

	// Insert an item for "b" so that "b" has 1 item but "a" has no item
	table1.appendEntry("b", 1);
	// Test deletion of row even though there is a different number
	// of items in the columns
	isDeleteSuccessful = table1.deleteRow(0);
	CPPUNIT_ASSERT_EQUAL(true, isDeleteSuccessful);

	/**--------------------------------------
	 *	Failures
	 *---------------------------------------*/
	// Now, table1 does not have any more rows left.
	// Try to delete row 0 (that does not exist)
	isDeleteSuccessful = table1.deleteRow(0);
	CPPUNIT_ASSERT_EQUAL(false, isDeleteSuccessful);

	// Try to delete row -1 (negative row)
	isDeleteSuccessful = table1.deleteRow(-1);
	CPPUNIT_ASSERT_EQUAL(false, isDeleteSuccessful);
}